共计 1430 个字符,预计需要花费 4 分钟才能阅读完成。
问题
我们在制作 Docker 镜像时,可以不从 Dockfile 进行构建,而是从一个基础镜像启动一个容器,登录该容器,进行一些初始化环境的操作,比如创建文件,删除文件等,最后将该容器提交为一个镜像。最后,发现制作出来的镜像体积很大,如何对该镜像进行瘦身呢?
原理
在给出具体方案前,先介绍下 Docker 的原理。Docker 使用存储驱动程序来存储镜像层和容器的可写层。
存储驱动程序针对空间效率进行了优化,但写入速度还是低于本机文件系统性能。
Dockerfile 中的 FROM
和 RUN
的行都会生成层,无论是生成文件或删除文件,都会产生新的层。这些层是 只读的,随着 Dockerfile 的增加,后边的层依赖前面的层,堆叠在一起。
镜像的大小,就是每层累积起来的大小,而镜像的每一层都是 只读层。最后的 CMD
表示在容器里运行的指令,是不会在镜像中产生层的。
容器和镜像之间的主要区别在于顶层可写层。当创建一个新容器时,会在镜像的层基础上,增加一个可写层(通常被称为“容器层”)。对正在运行的容器所做的所有更改,例如写入新文件、修改现有文件和删除文件,都将写入这个薄的可写容器层(Thin R/W layer)
。当容器被删除时,可写层也被删除。
因为每个容器都有自己的可写容器层,所以多个容器可以共享对同一底层镜像的访问,但保持有自己的数据状态。如下图所示:
写时复制 (CoW) 是一种高效的共享 / 复制文件策略。如果一个文件或目录存在于镜像中的较低层,而另一更高层(包括可写层)需要对其进行读取访问时:文件被从低层复制到高层并进行修改。这最大限度地减少了 I/O 和每个后续层的大小。
裁剪现有镜像
回到最初的问题,当我们通过容器对初始化环境完成后,通过 docker commit {container id
} 将容器打包成镜像,然后,通过 docker tag {image id} {tag}
对新制作的镜像打 tag。然后,通过 docker inspect {tag}
查看到新制作出来的镜像是两层,整个镜像的大小是两层的累积。例如 hadoop 3.3.5 使用 docker 编译源码 制作出来的 tag 为suizhe007/hadoop:v3.3.5.1
镜像为 11.8GB,运行命令docker inspect suizhe007/hadoop:v3.3.5.1
,结果下图所示:
如何裁剪瘦身呢?
原理上一节已经介绍过了,我们首先将镜像中的缓存文件和中间文件等删除掉,然后将 Layer 合并为一个,就达到了镜像瘦身的目的。具体做法如下:
首先将该镜像 suizhe007/hadoop:v3.3.5.1
以容器方式启动,运行如下命令:
docker run -i -t suizhe007/hadoop:v3.3.5.1
裁剪现有镜像需要使用 docker export
+ docker import
将容器的状态,导出成一层然后导入,这样删除起作用了。本质上是 通过丢弃层累积的信息 来实现的。
导出命令如下:
docker export 1bd5f87e > hadoop3-v1.tar
导入容器 tar 文件,得到 单层 镜像,导入命令如下:
docker import hadoop3-v1.tar
如下图所示,得到的 suizhe007/hadoop:v3.3.5.2
大小仅为 2.86GB,大大缩减镜像的大小:
后记
相信通过本文,你已经对 docker 镜像和容器的原理有所了解,以及对现有体积巨大的容器如何瘦身也有一定的优化思路,关于更深入的 docker 方面的知识,后续还会继续分享。